package org.liaohailong.library.widget.crop

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.*
import android.os.AsyncTask
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import kotlin.math.abs

/**
 * Describe: 图片裁剪
 *
 *
 * @author liaohailong
 * Time: 2020/9/7 19:43
 */
class CropImageView(context: Context, attributeSet: AttributeSet?, defStyle: Int) : View(context, attributeSet, defStyle) {
    constructor(context: Context, attributeSet: AttributeSet?) : this(context, attributeSet, 0)
    constructor(context: Context) : this(context, null, 0)

    /**
     * 原图
     */
    private var bitmap: Bitmap? = null

    /**
     * 裁剪比例
     */
    private var cropRatio: Float = 1f

    /**
     * 绘制图片
     */
    private val bitmapPaint = Paint().apply {
        isAntiAlias = true
        isFilterBitmap = true
    }

    /**
     * 绘制裁剪区域
     */
    private val cropAreaPaint = Paint().apply {
        isAntiAlias = true
        style = Paint.Style.FILL
        color = Color.parseColor("#80EEEEEE")
    }

    /**
     * 绘制裁剪变线
     */
    private val cropStrokePaint = Paint().apply {
        isAntiAlias = true
        style = Paint.Style.STROKE
        strokeWidth = resources.displayMetrics.density
        color = Color.parseColor("#EEEEEE")
    }

    /**
     * 手势操作点
     */
    private val gesturePointPaint = Paint().apply {
        isAntiAlias = true
        style = Paint.Style.FILL_AND_STROKE
        color = Color.YELLOW
    }


    /**
     * 原图绘制区域
     */
    private val bitmapRect: Rect = Rect()
    private val drawRect: Rect = Rect()

    /**
     * 裁剪区域
     */
    private val cropAreaRect: Rect = Rect()
    private var minCropLength = Int.MAX_VALUE

    /**
     * 视图可见区域
     */
    private val viewRect: Rect = Rect()

    /**
     * 左上角 - 操作提示点
     */
    private val leftTopPointRect = Rect()

    /**
     * 右下角 - 操作提示点
     */
    private val rightBottomPointRect = Rect()


    // 图片最大显示宽度
    private var maxWidth: Int = 0

    // 图片最大显示高度
    private var maxHeight: Int = 0

    // 手势操作提示圆点半径
    private var gesturePointRadius: Float = 0F

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        maxWidth = w
        maxHeight = h
        // 视图可见区域就是视图绘制区域
        viewRect.set(paddingLeft, paddingRight, maxWidth - paddingRight, maxHeight - paddingBottom)
        // 暂定手势操作点就是最短边长的 1/10
        gesturePointRadius = Math.min(maxWidth, maxHeight) / 20f
    }


    /**
     * 设置裁剪裁剪比例
     * @param ratio 宽：高
     */
    fun setCropRatio(ratio: Float) {
        this@CropImageView.cropRatio = ratio
    }

    /**
     * 设置位图
     * @param bitmap 源图
     */
    fun setBitmap(bitmap: Bitmap) {
        this@CropImageView.bitmap = bitmap

        // 必须要再视图加载完成后添加
        post {
            // 自身控件建议match整个activity
            if (maxWidth <= 0 || maxHeight < 0) return@post

            calculateBitmapRect()
            calculateCropAreaRect()

            // 记得重绘
            postInvalidate()
        }
    }

    /**
     * 裁剪当前区域
     */
    fun crop(callback: OnImageCropCallback) {
        AsyncTask.THREAD_POOL_EXECUTOR.execute {
            bitmap?.apply {
                // 计算原图与视图缩放比例，裁剪区域也需要根据此比例先换算
                val showWidth = bitmapRect.width()
                val showHeight = bitmapRect.height()
                val bitmapWidth = width
                val bitmapHeight = height

                val scaleW = bitmapWidth.toFloat() / showWidth.toFloat()
                val scaleH = bitmapHeight.toFloat() / showHeight.toFloat()

                // 视图偏移量 * 视图缩放比 = 裁剪偏移量
                val cropLeft = (cropAreaRect.left - bitmapRect.left) * scaleW
                val cropTop = (cropAreaRect.top - bitmapRect.top) * scaleH
                val cropWidth = cropAreaRect.width() * scaleW
                val cropHeight = cropAreaRect.height() * scaleH

                val result = Bitmap.createBitmap(this,
                        cropLeft.toInt(),
                        cropTop.toInt(),
                        cropWidth.toInt(),
                        cropHeight.toInt(),
                        Matrix(), true)

                post { callback.onImageCrop(result) }
            }
        }
    }


    /**
     * 计算图片显示区域
     */
    private fun calculateBitmapRect() {
        // 将图片完全显示在视图内 - centerSide
        bitmap?.apply {
            val availableWidth = maxWidth - paddingLeft - paddingRight
            val availableHeight = maxHeight - paddingTop - paddingBottom

            val scaleW = availableWidth.toFloat() / width.toFloat()
            val scaleH = availableHeight.toFloat() / height.toFloat()
            val scale = Math.min(scaleW, scaleH)

            val bitmapWidth = width * scale
            val bitmapHeight = height * scale

            // 计算图片显示区域坐标
            val left = (availableWidth - bitmapWidth) / 2f
            val top = (availableHeight - bitmapHeight) / 2f
            val right = left + bitmapWidth
            val bottom = top + bitmapHeight

            // 图片绘制区域
            bitmapRect.set(left.toInt(), top.toInt(), right.toInt(), bottom.toInt())

            // 最少裁剪尺寸
            minCropLength = (gesturePointRadius * 2).toInt()
        }
    }

    /**
     * 计算裁剪显示区域
     */
    private fun calculateCropAreaRect() {
        // 默认让裁剪区域正方形居中显示 - 贴住图片最短的那条边
        val bitmapWidth = bitmapRect.width()
        val bitmapHeight = bitmapRect.height()

        // 找出最小的边长，再与裁剪比例值进行换算
        val length = Math.min(bitmapWidth, bitmapHeight)
        var cropWidth: Int = length
        var cropHeight: Int = (cropWidth / cropRatio).toInt()

        // 可能裁剪区域会超出图片范围，这里需要等比例缩放
        val cropScaleW = bitmapWidth.toFloat() / cropWidth.toFloat()
        val cropScaleH = bitmapHeight.toFloat() / cropHeight.toFloat()
        val cropScale = Math.min(cropScaleW, cropScaleH)
        cropWidth = (cropWidth * cropScale).toInt()
        cropHeight = (cropHeight * cropScale).toInt()

        val cropLeft = bitmapRect.left + (bitmapWidth - cropWidth) / 2f
        val cropTop = bitmapRect.top + (bitmapHeight - cropHeight) / 2f
        val cropRight = cropLeft + cropWidth
        val cropBottom = cropTop + cropHeight
        cropAreaRect.set(cropLeft.toInt(), cropTop.toInt(), cropRight.toInt(), cropBottom.toInt())

        // 预留两个操作点全部可见 - 模拟两指缩放一下
        val zoomIn = 1.0f - (gesturePointRadius * 2f / (cropAreaRect.width().toFloat() + gesturePointRadius))
        val matrix = Matrix()
        matrix.setScale(zoomIn, zoomIn, cropAreaRect.centerX().toFloat(), cropAreaRect.centerY().toFloat())
        val rectF = RectF(cropAreaRect)
        matrix.mapRect(rectF)
        cropAreaRect.set(rectF.left.toInt(), rectF.top.toInt(), rectF.right.toInt(), rectF.bottom.toInt())

        calculateAnchor()
    }

    /**
     * 计算操作点位置
     */
    private fun calculateAnchor() {
        val leftTopPointLeft = (cropAreaRect.left - gesturePointRadius).toInt()
        val leftTopPointTop = (cropAreaRect.top - gesturePointRadius).toInt()
        val leftTopPointRight = (cropAreaRect.left + gesturePointRadius).toInt()
        val leftTopPointBottom = (cropAreaRect.top + gesturePointRadius).toInt()
        leftTopPointRect.set(leftTopPointLeft, leftTopPointTop, leftTopPointRight, leftTopPointBottom)

        val rightBottomPointLeft = (cropAreaRect.right - gesturePointRadius).toInt()
        val rightBottomPointTop = (cropAreaRect.bottom - gesturePointRadius).toInt()
        val rightBottomPointRight = (cropAreaRect.right + gesturePointRadius).toInt()
        val rightBottomPointBottom = (cropAreaRect.bottom + gesturePointRadius).toInt()
        rightBottomPointRect.set(rightBottomPointLeft, rightBottomPointTop, rightBottomPointRight, rightBottomPointBottom)
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.apply {
            drawContent(this)
            drawCropArea(this)
        }
    }

    private fun drawContent(canvas: Canvas) {
        bitmap?.apply {
            drawRect.set(0, 0, width, height)
            canvas.drawBitmap(this, drawRect, bitmapRect, bitmapPaint)
        }
    }

    private fun drawCropArea(canvas: Canvas) {
        // 绘制裁剪区域
        canvas.drawRect(cropAreaRect, cropAreaPaint)

        // 绘制裁剪边线
        canvas.drawRect(cropAreaRect, cropStrokePaint)

        // 绘制手势操作提示点 - 左上
        var cx = leftTopPointRect.centerX().toFloat()
        var cy = leftTopPointRect.centerY().toFloat()
        val radius = gesturePointRadius
        canvas.drawCircle(cx, cy, radius, gesturePointPaint)
        // 绘制手势操作提示点 - 右下
        cx = rightBottomPointRect.centerX().toFloat()
        cy = rightBottomPointRect.centerY().toFloat()
        canvas.drawCircle(cx, cy, radius, gesturePointPaint)
    }

    /**
     * 手势操作状态，用于控制裁剪区域
     */
    private var touchMode: TouchMode = TouchMode.NONE
    private var lastTouchPoint: Point = Point()

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        event?.apply {
            when (action) {
                MotionEvent.ACTION_DOWN -> {
                    // 手指按下，判断手势
                    val touchX = x.toInt()
                    val touchY = y.toInt()
                    // 操作左上角控制点
                    touchMode = when {
                        leftTopPointRect.contains(touchX, touchY) -> {
                            TouchMode.RESIZE_LEFT_TOP
                        }

                        // 操作右下角控制点
                        rightBottomPointRect.contains(touchX, touchY) -> {
                            TouchMode.RESIZE_RIGHT_BOTTOM
                        }

                        // 移动裁剪区域
                        cropAreaRect.contains(touchX, touchY) -> {
                            TouchMode.MOVING_CROP
                        }

                        // 无操作
                        else -> {
                            TouchMode.NONE
                        }
                    }

                    lastTouchPoint.set(touchX, touchY)
                }
                MotionEvent.ACTION_MOVE -> {
                    val touchX = x.toInt()
                    val touchY = y.toInt()
                    val dx = touchX - lastTouchPoint.x
                    val dy = touchY - lastTouchPoint.y

                    when (touchMode) {
                        TouchMode.NONE -> {
                            // do nothing ...
                        }
                        TouchMode.RESIZE_LEFT_TOP -> {
                            updateLeftTopPoint(dx, dy)
                        }
                        TouchMode.RESIZE_RIGHT_BOTTOM -> {
                            updateRightBottomPoint(dx, dy)
                        }
                        TouchMode.MOVING_CROP -> {
                            updateCropArea(dx, dy)
                        }
                    }

                    lastTouchPoint.set(touchX, touchY)
                }
                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                    touchMode = TouchMode.NONE
                }
            }
        }
        // 有效的手势操作记得重绘视图
        if (touchMode != TouchMode.NONE) invalidate()
        return true
    }

    private fun updateLeftTopPoint(dx: Int, dy: Int) {
        val left = cropAreaRect.left
        val top = cropAreaRect.top

        // 选一个最大滑动距离,等比例缩放
        val useX = abs(dx) >= abs(dy)
        val offset = if (useX) dx else dy

        val transLeft = left + offset
        val transTop = top + (offset / cropRatio).toInt()
        val transRight = cropAreaRect.right
        val transBottom = cropAreaRect.bottom

        // 限制条件
        val minX = bitmapRect.left
        val minY = bitmapRect.top

        val positionOk = minX <= transLeft && minY <= transTop

        val cropWidth = transRight - transLeft
        val cropHeight = transBottom - transTop

        val lengthOk = minCropLength <= cropWidth && minCropLength <= cropHeight

        if (positionOk && lengthOk) {
            cropAreaRect.left = transLeft
            cropAreaRect.top = transTop

            calculateAnchor()
        }
    }

    private fun updateRightBottomPoint(dx: Int, dy: Int) {
        val right = cropAreaRect.right
        val bottom = cropAreaRect.bottom

        // 选一个最大滑动距离,等比例缩放
        val useX = abs(dx) >= abs(dy)
        val offset = if (useX) dx else dy

        val transLeft = cropAreaRect.left
        val transTop = cropAreaRect.top
        val transRight = right + offset
        val transBottom = bottom + (offset / cropRatio).toInt()

        // 限制条件
        val maxX = bitmapRect.right
        val maxY = bitmapRect.bottom

        val positionOK = transRight <= maxX && transBottom <= maxY

        val cropWidth = transRight - transLeft
        val cropHeight = transBottom - transTop

        val lengthOk = minCropLength <= cropWidth && minCropLength <= cropHeight

        if (positionOK && lengthOk) {
            cropAreaRect.right = transRight
            cropAreaRect.bottom = transBottom

            calculateAnchor()
        }
    }

    private fun updateCropArea(dx: Int, dy: Int) {
        val left = cropAreaRect.left
        val top = cropAreaRect.top
        val right = cropAreaRect.right
        val bottom = cropAreaRect.bottom

        var transLeft = left + dx
        var transTop = top + dy
        var transRight = right + dx
        var transBottom = bottom + dy

        val minX = bitmapRect.left
        val minY = bitmapRect.top
        val maxX = bitmapRect.right
        val maxY = bitmapRect.bottom

        // 边界限制
        if (transLeft <= minX || maxX <= transRight) {
            transLeft = left
            transRight = right
        }
        if (transTop <= minY || maxY <= transBottom) {
            transTop = top
            transBottom = bottom
        }

        cropAreaRect.set(transLeft, transTop, transRight, transBottom)
        calculateAnchor()
    }

    enum class TouchMode {
        /**
         * 无操作
         */
        NONE,

        /**
         * 修改尺寸 - 左上操作点
         */
        RESIZE_LEFT_TOP,

        /**
         * 修改尺寸 - 右下操作点
         */
        RESIZE_RIGHT_BOTTOM,

        /**
         * 移动裁剪区域
         */
        MOVING_CROP,
    }

    /**
     * 图片裁剪完成回调
     */
    interface OnImageCropCallback {
        fun onImageCrop(bitmap: Bitmap)
    }
}